package org.moxie;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.util.StringTokenizer;

/**
 * Default implementation of artifact versioning.
 * <p>
 * Forked from Maven3 DefaultArtifactVersion @ SVN1084807
 * 
 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
 * 
 */
public class ArtifactVersion implements Comparable<ArtifactVersion> {
	private Integer majorVersion;

	private Integer minorVersion;

	private Integer incrementalVersion;

	private Integer buildNumber;

	private String qualifier;

	private ComparableVersion comparable;

	public ArtifactVersion(String version) {
		parseVersion(version);
	}

	@Override
	public int hashCode() {
		return 11 + comparable.hashCode();
	}

	@Override
	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}

		if (!(other instanceof ArtifactVersion)) {
			return false;
		}

		return compareTo((ArtifactVersion) other) == 0;
	}

	public int compareTo(ArtifactVersion otherVersion) {
		if (otherVersion instanceof ArtifactVersion) {
			return this.comparable.compareTo(((ArtifactVersion) otherVersion).comparable);
		} else {
			return compareTo(new ArtifactVersion(otherVersion.toString()));
		}
	}

	public int getMajorVersion() {
		return majorVersion != null ? majorVersion : 0;
	}

	public int getMinorVersion() {
		return minorVersion != null ? minorVersion : 0;
	}

	public int getIncrementalVersion() {
		return incrementalVersion != null ? incrementalVersion : 0;
	}

	public int getBuildNumber() {
		return buildNumber != null ? buildNumber : 0;
	}

	public String getQualifier() {
		return qualifier;
	}

	public final void parseVersion(String version) {
		comparable = new ComparableVersion(version);

		int index = version.indexOf("-");

		String part1;
		String part2 = null;

		if (index < 0) {
			part1 = version;
		} else {
			part1 = version.substring(0, index);
			part2 = version.substring(index + 1);
		}

		if (part2 != null) {
			try {
				if ((part2.length() == 1) || (part2.charAt(0) != '0')) {
					buildNumber = Integer.valueOf(part2);
				} else {
					qualifier = part2;
				}
			} catch (NumberFormatException e) {
				qualifier = part2;
			}
		}

		if ((part1.indexOf(".") < 0) && (part1.charAt(0) != '0')) {
			try {
				majorVersion = Integer.valueOf(part1);
			} catch (NumberFormatException e) {
				// qualifier is the whole version, including "-"
				qualifier = version;
				buildNumber = null;
			}
		} else {
			boolean fallback = false;

			StringTokenizer tok = new StringTokenizer(part1, ".");
			try {
				majorVersion = getNextIntegerToken(tok);
				if (tok.hasMoreTokens()) {
					minorVersion = getNextIntegerToken(tok);
				}
				if (tok.hasMoreTokens()) {
					incrementalVersion = getNextIntegerToken(tok);
				}
				if (tok.hasMoreTokens()) {
					fallback = true;
				}

				// string tokenzier won't detect these and ignores them
				if (part1.indexOf("..") >= 0 || part1.charAt(0) == '.' || part1.endsWith(".")) {
					fallback = true;
				}
			} catch (NumberFormatException e) {
				fallback = true;
			}

			if (fallback) {
				// qualifier is the whole version, including "-"
				qualifier = version;
				majorVersion = null;
				minorVersion = null;
				incrementalVersion = null;
				buildNumber = null;
			}
		}
	}

	private static Integer getNextIntegerToken(StringTokenizer tok) {
		String s = tok.nextToken();
		if ((s.length() > 1) && s.startsWith("0")) {
			throw new NumberFormatException("Number part has a leading 0: '" + s + "'");
		}
		return Integer.valueOf(s);
	}

	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();
		if (majorVersion != null) {
			buf.append(majorVersion);
		}
		if (minorVersion != null) {
			buf.append('.');
			buf.append(minorVersion);
		}
		if (incrementalVersion != null) {
			buf.append('.');
			buf.append(incrementalVersion);
		}
		if (buildNumber != null) {
			buf.append('-');
			buf.append(buildNumber);
		} else if (qualifier != null) {
			if (buf.length() > 0) {
				buf.append('-');
			}
			buf.append(qualifier);
		}
		return buf.toString();
	}
	
	public static enum NumberField {
		major, minor, incremental, build;
	}
	
	public ArtifactVersion incrementMajorVersion() {
		return increment(NumberField.major, 1);		
	}

	public ArtifactVersion incrementMinorVersion() {
		return increment(NumberField.minor, 1);
	}

	public ArtifactVersion incrementIncrementalVersion() {
		return increment(NumberField.incremental, 1);
	}

	public ArtifactVersion incrementBuildNumber() {
		return increment(NumberField.build, 1);
	}
	
	private ArtifactVersion increment(NumberField v, int step) {
		switch (v) {
		case major:
			majorVersion = getMajorVersion() + step;
			if (minorVersion != null) {
				minorVersion = 0;
			}
			if (incrementalVersion != null) {
				incrementalVersion = 0;
			}
			incrementalVersion = null;
			buildNumber = null;
			break;
		case minor:
			minorVersion = getMinorVersion() + step;
			if (incrementalVersion != null) {
				incrementalVersion = 0;
			}
			buildNumber = null;
			break;
		case incremental:
			incrementalVersion = getIncrementalVersion() + step;
			buildNumber = null;
			break;
		case build:
			buildNumber = getBuildNumber() + step;
			default:
				break;
		}
		return this;
	}
	
	public boolean isSnapshot() {
		return "SNAPSHOT".equalsIgnoreCase(qualifier);
	}
	
	public ArtifactVersion setSnapshot(boolean isSnapshot) {
		if (isSnapshot) {
			qualifier = "SNAPSHOT";
		} else {
			qualifier = null;
		}
		return this;
	}
}
